package com.shelpe.services.algorithm.service;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.context.request.async.DeferredResult;

import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.shelpe.services.algorithm.autoDrive.RecommendWordFilter;
import com.shelpe.services.algorithm.choose.ChooseHandler;
import com.shelpe.services.algorithm.client.LexiconClient;
import com.shelpe.services.algorithm.client.LexiconClient.AdgroupRecordDto;
import com.shelpe.services.algorithm.client.LexiconClient.RecommendWordDto;
import com.shelpe.services.algorithm.client.LexiconClient.Response;
import com.shelpe.services.algorithm.client.SinbadClient;
import com.shelpe.services.algorithm.controller.AutoProductQueueClient;
import com.shelpe.services.algorithm.entity.jpa.Adgroup;
import com.shelpe.services.algorithm.entity.jpa.AutoProductQueue;
import com.shelpe.services.algorithm.entity.jpa.Product;
import com.shelpe.services.algorithm.repository.jpa.AdgroupRepository;
import com.shelpe.services.algorithm.repository.jpa.AutoProductQueueRepository;
import com.shelpe.services.algorithm.repository.jpa.ProductRepository;
import com.shelpe.services.algorithm.utils.AutoDriverUtils;

import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import rx.Observable;
import rx.functions.Action1;

@Service
@Slf4j
public class AutoProductQueueService {

	@Autowired
	private AutoProductQueueRepository autoProductQueueRepository;
	@Autowired
	private ProductRepository productRepository;
	@Autowired
	private AdgroupRepository adgroupRepository;
	@Autowired
	private SinbadClient sinbadClient;
	@Autowired
	private LexiconClient lexiconClient;
	@Autowired
	private ObjectMapper objectMapper;
	@Autowired
	private List<RecommendWordFilter> recommendWordFilters;
	@Autowired
	private List<ChooseHandler<Adgroup>> chooseHandlers;

	/**
	 * 自动开车的第一步 － 创建推广组和获取相关淘宝数据（推荐词，大盘数据）
	 * 
	 * @param result
	 * @param queue
	 * @param defaultPrice
	 */
	public void autoDrive(final DeferredResult<SinbadClient.AdgroupDto> result, final AutoProductQueue queue,
			int defaultPrice) {

		// start to drive
		queue.setProgress(0);
		queue.setStatus(AutoProductQueue.Status.updating);
		queue.setLastModifiedTime(new Date());
		this.autoProductQueueRepository.save(queue);

		// 核实产品
		log.debug("check product: {} {}", queue.getCampaignID(), queue.getProductID());
		Product product = this.productRepository.findOne(queue.getProductID());
		if (product == null || StringUtils.isBlank(product.getProductName())
				|| StringUtils.isBlank(product.getPictureUrl())) {
			log.warn("invalid product: {}", product);
			queue.setStatus(AutoProductQueue.Status.failed);
			this.checkSuccess(queue.getCampaignID(), queue.getSequence(), queue.getUserID());
			String msg = "找不到产品";
			if (product != null) {
				msg = "没有产品" + (StringUtils.isBlank(product.getProductName()) ? "标题." : "图片.");
			}
			queue.setMessage(msg);
			queue.setLastModifiedTime(new Date());
			this.autoProductQueueRepository.save(queue);
			result.setResult(new SinbadClient.AdgroupDto());
			return;
		}

		// 创建推广组
		log.debug("create adgroup: {} {}", queue.getCampaignID(), queue.getProductID());
		final SinbadClient.AdgroupDto adgroup = new SinbadClient.AdgroupDto();
		adgroup.setUserID(queue.getUserID());
		adgroup.setCampaignID(queue.getCampaignID());
		adgroup.setProductID(product.getProductID());
		adgroup.setLimit(queue.getMaxPrice());
		String title = queue.getFirstCreativeName();
		if (title == null) {
			title = product.getProductName();
		}
		if (title != null && title.length() > 20) {
			title = title.substring(0, 20);
		}
		adgroup.setProductName(title);
		String pic = queue.getFirstCreativeUrl();
		if (pic == null) {
			pic = product.getPictureUrl();
		}
		adgroup.setProductUrl(pic);
		if (product.getItemImgs() != null) {
			if (StringUtils.isNotBlank(queue.getSecondCreativeName())) {
				queue.setSecondCreativeName(product.getProductName() != null && product.getProductName().length() > 20
						? product.getProductName().substring(product.getProductName().length() - 20,
								product.getProductName().length())
						: product.getProductName());
				adgroup.setCreativeName(queue.getSecondCreativeName());
			}
			if (StringUtils.isNotBlank(queue.getSecondCreativeUrl())) {
				queue.setSecondCreativeUrl(product.getItemImgs().iterator().next());
				adgroup.setCreativeImgUrl(queue.getSecondCreativeUrl());
			}
		}
		if (defaultPrice == 0) {
			defaultPrice = queue.getDefaultPrice();
		}
		defaultPrice = Math.max(defaultPrice, 5);// 代码保护,缺省不能小于5
		adgroup.setDefaultPrice(defaultPrice);
		if (queue.getFlag() == 1) {
			adgroup.setDriveType(2);
		} else {
			adgroup.setDriveType(1);
		}

		if (adgroup.getDefaultPrice() == 0) {// fail if zero
			adgroup.setDefaultPrice(5);
		}
		SinbadClient.AdgroupDto adgroupRes = this.sinbadClient.createAdgroup(adgroup);
		log.debug("adgroup is created: {} {} {} {}", queue.getCampaignID(), queue.getProductID(),
				adgroup.getAdgroupID(), adgroupRes);
		adgroup.setAdgroupID(adgroupRes.getAdgroupID());
		if (adgroupRes.getAdgroupID() <= 0) {
			log.warn("fail to create adgroup: {}", adgroup);
			queue.setStatus(AutoProductQueue.Status.failed);
			this.checkSuccess(queue.getCampaignID(), queue.getSequence(), queue.getUserID());
			queue.setMessage("创建推广组失败");
			if (adgroupRes.getError() != null && adgroupRes.getError().getSubMessage() != null) {
				if ("adgroup.item.is.repeat".equals(adgroupRes.getError().getSubMessage())) {
					queue.setMessage(queue.getMessage() + "(原因是在同一推广计划推同一个宝贝)");
				} else {
					queue.setMessage(queue.getMessage() + "(原因为淘宝" + adgroupRes.getError().getSubMessage() + ")");
				}
			} else {
				queue.setMessage(queue.getMessage() + "(原因为淘宝响应超时)");
			}
			queue.setLastModifiedTime(new Date());
			this.autoProductQueueRepository.save(queue);
			result.setResult(adgroup);
			return;
		}

		// 修改时间折扣
		SinbadClient.CampaignDto campaignDto = new SinbadClient.CampaignDto();
		String schedulePreDay = "";
		if (queue.getFlag() == 1) {
			schedulePreDay = "08:30-17:00:70,20:00-23:00:100";
		} else {
			schedulePreDay = "00:00-07:00:30,07:00-09:00:60,09:00-11:00:100,11:00-13:00:80,13:00-17:00:100,17:00-19:00:80,19:00-23:00:110,23:00-24:00:60";
		}
		StringBuffer buffer = new StringBuffer();
		for (int i = 1; i <= 7; i++) {
			buffer.append(schedulePreDay);
			if (i < 7) {
				buffer.append(";");
			}
		}
		campaignDto.setSchdule(buffer.toString());
		campaignDto.setUserID(queue.getUserID());
		campaignDto = this.sinbadClient.updateCampaign(queue.getCampaignID(), campaignDto);
		if (campaignDto.getCampaignID() > 0) {
			log.debug("success to update campaigin: {}", campaignDto);
		} else {
			log.error("fail to update campaigin: {}", queue);
		}

		queue.setAdgroupID(adgroup.getAdgroupID());
		queue.setProgress(10);
		queue.setStatus(AutoProductQueue.Status.updating);
		queue.setLastModifiedTime(new Date());
		this.autoProductQueueRepository.save(queue);

		// update adgroup state
		log.debug("update adgroup state as need confirm: {} {}", queue.getCampaignID(), queue.getProductID());
		if (queue.isNeedConfirm()) {
			adgroup.setStatus(-1);
			adgroup.setLineStatus("offline");
			this.sinbadClient.updateAdgroupStatus(adgroup.getAdgroupID(), adgroup);
		}

		// 同步推荐词和大盘数据
		log.debug("sync recommends: {}", queue);
		// 推荐词
		Observable<LexiconClient.Response> lexiconOb = this.lexiconClient.createAdgroupRecord(adgroup.getAdgroupID(),
				new AdgroupRecordDto(true));
		lexiconOb.subscribe(new Action1<LexiconClient.Response>() {
			@Override
			public void call(Response lexiconRes) {
				log.debug("sync recommends completed: {} {}", queue, lexiconRes);
				if (!lexiconRes.isStatus()) {
					log.error("fail to sync recommends: {}", queue);
					adgroup.setStatus(-1);
					queue.setStatus(AutoProductQueue.Status.failed);
					checkSuccess(queue.getCampaignID(), queue.getSequence(), queue.getUserID());
					queue.setMessage("同步推荐词失败");
					if (lexiconRes.getError() != null && lexiconRes.getError().getSubMessage() != null) {
						queue.setMessage(queue.getMessage() + "(原因为淘宝" + lexiconRes.getError().getSubMessage() + ")");
					} else {
						queue.setMessage(queue.getMessage() + "(原因为淘宝响应超时)");
					}
				} else {
					// 只是纪录一下，有可能返回了组合词
					if (lexiconRes.getError() != null && lexiconRes.getError().getSubMessage() != null) {
						queue.setMessage("同步淘宝推荐词失败(原因为" + lexiconRes.getError().getSubMessage() + ")");
					}
					queue.setProgress(20);
					log.debug("sync insight: {} {}", queue.getCampaignID(), queue.getProductID());
					// 大盘数据
					LexiconClient.AdgroupMetricDto dto = new LexiconClient.AdgroupMetricDto();
					Observable<LexiconClient.Response> metricOb = lexiconClient
							.syncAdgroupRecordMetrics(adgroup.getAdgroupID(), dto);
					metricOb.subscribe(new Action1<LexiconClient.Response>() {
						@Override
						public void call(Response res) {
							log.debug("sync insight completed: {} {}", queue, res);
							result.setResult(adgroup);
						}
					}, new Action1<Throwable>() {
						@Override
						public void call(Throwable t) {
							log.error("sync insight error: {} {}", queue, t);
							result.setResult(adgroup);
						}

					});
				}
				queue.setLastModifiedTime(new Date());
				autoProductQueueRepository.save(queue);
			}
		}, new Action1<Throwable>() {

			@Override
			public void call(Throwable t) {
				log.error("sync recommends error: {} {}", queue, t);
				adgroup.setStatus(-1);
				queue.setStatus(AutoProductQueue.Status.failed);
				checkSuccess(queue.getCampaignID(), queue.getSequence(), queue.getUserID());
				queue.setMessage("同步推广组数据失败(原因为淘宝响应超时)");
				queue.setLastModifiedTime(new Date());
				autoProductQueueRepository.save(queue);

				result.setResult(adgroup);
			}

		});

	}

	public void supplementInsight(final DeferredResult<AutoProductQueueClient.AutoDrivingDto> result,
			final AutoProductQueue queue, final Adgroup adgroup) {
		final AutoProductQueueClient.AutoDrivingDto retDto = new AutoProductQueueClient.AutoDrivingDto();
		retDto.setAdgroupID(adgroup.getAdgroupID());
		retDto.setCampaignID(adgroup.getCampaignID());
		retDto.setProductID(adgroup.getProductID());
		retDto.setDefaultPrice(adgroup.getDefaultPrice());

		LexiconClient.AdgroupMetricDto dto = new LexiconClient.AdgroupMetricDto();
		Observable<LexiconClient.Response> metricOb = lexiconClient.syncAdgroupRecordMetrics(adgroup.getAdgroupID(),
				dto);
		metricOb.subscribe(new Action1<LexiconClient.Response>() {
			@Override
			public void call(Response res) {
				log.debug("sync insight completed: {} {}", queue, res);
				retDto.setSuccess(true);
				result.setResult(retDto);
			}
		}, new Action1<Throwable>() {
			@Override
			public void call(Throwable t) {
				log.error("sync insight error: {} {}", queue, t);
				retDto.setSuccess(false);
				retDto.setMessage("invoke_failure");
				result.setResult(retDto);
			}

		});
	}

	public boolean chooseWord(AutoProductQueue queue, int defaultPrice) {
		// generate keyword
		log.debug("generate words: {} {}", queue.getCampaignID(), queue.getProductID(), defaultPrice);
		List<LexiconClient.RecommendWordDto> recommendWords = this.lexiconClient.smartWordsAsDevice(
				queue.getAdgroupID(), AutoDriverUtils.covert2mode(queue.getType()), (queue.getFlag() == 1) ? 1 : 0, 0,
				((float) queue.getMaxPrice()) / 100);
		if (recommendWords == null || recommendWords.size() == 0) {
			log.warn("no recommend words: {}", queue);
			queue.setStatus(AutoProductQueue.Status.failed);
			this.checkSuccess(queue.getCampaignID(), queue.getSequence(), queue.getUserID());
			if (queue.getMessage() != null) {
				queue.setMessage("没有推荐关键词," + queue.getMessage());
			} else {
				queue.setMessage("没有推荐关键词。");
			}
			queue.setLastModifiedTime(new Date());
			this.autoProductQueueRepository.save(queue);
			return false;
		}
		queue.setProgress(40);
		queue.setStatus(AutoProductQueue.Status.updating);
		queue.setLastModifiedTime(new Date());
		this.autoProductQueueRepository.save(queue);

		Adgroup adgroup = this.adgroupRepository.findOne(queue.getAdgroupID());
		if (recommendWords == null || recommendWords.size() == 0) {
			log.warn("adgroup is not found: {}", queue);
			queue.setStatus(AutoProductQueue.Status.failed);
			this.checkSuccess(queue.getCampaignID(), queue.getSequence(), queue.getUserID());
			queue.setMessage("找不到推广组(原因为淘宝响应超时)");
			queue.setLastModifiedTime(new Date());
			this.autoProductQueueRepository.save(queue);
			return false;
		}

		// add keyword to adgroup
		log.debug("add words to adgroup: {} {}", queue.getCampaignID(), queue.getProductID());
		List<SinbadClient.HandlingKeyword> handlingWords = null;
		for (RecommendWordFilter filter : this.recommendWordFilters) {
			if (filter.support(adgroup, queue)) {
				handlingWords = filter.filter(recommendWords, queue, adgroup);
				break;
			}
		}
		if (handlingWords == null) {
			log.debug("no recommend word filter: {}", queue);
			return false;
		}
		log.debug("there is {} recommend words", handlingWords.size());

		int valve = 100;
		if (this.chooseHandlers != null) {
			ChooseHandler.ChooseHandleContext<Adgroup> context = new ChooseHandler.ChooseHandleContext<Adgroup>();
			context.setEntity(adgroup);
			for (ChooseHandler<Adgroup> handler : this.chooseHandlers) {
				if (handler.support(context)) {
					valve = handler.count(context);
					break;
				}
			}
		}
		valve = Math.max(valve, 1);

		if (handlingWords.size() > valve) {
			handlingWords = handlingWords.subList(0, valve);
		}
		if (log.isDebugEnabled()) {
			for (SinbadClient.HandlingKeyword keyword : handlingWords) {
				log.debug("to add word: {}", keyword);
			}
		}
		if (handlingWords.size() > 0) {
			SinbadClient.ListResponse<SinbadClient.PricingKeyword> ret = this.sinbadClient
					.addKeywords(queue.getAdgroupID(), handlingWords);
			log.debug("add keywords ret: {}", ret);
			if (ret.getDatas() != null && ret.getDatas().size() > 0) {
				if (handlingWords.size() != ret.getDatas().size()) {
					log.error("some keywords fail to added: {}", queue);
				}
			} else {
				log.error("no keyword is success to added: {}", queue);
			}
		} else {
			log.error("no recommend words: {}", queue);
		}
		queue.setInitialKeywordCount(handlingWords.size());
		queue.setProgress(60);
		queue.setStatus(AutoProductQueue.Status.updating);
		queue.setLastModifiedTime(new Date());
		this.autoProductQueueRepository.save(queue);

		log.debug("save candwords: {} {}", queue.getCampaignID(), queue.getProductID());
		List<String> usedWords = new ArrayList<String>();
		if (queue.getCandWords() != null) {
			usedWords.addAll(this.parseWords(queue.getCandWords()));
		}
		List<String> choosenWords = new ArrayList<String>();
		for (RecommendWordDto dto : recommendWords) {
			choosenWords.add(dto.getWord());
		}
		usedWords.addAll(choosenWords);
		queue.setCandWords(this.encodeWords(usedWords));

		log.debug("finish: {} {}", queue.getCampaignID(), queue.getProductID());
		queue.setProgress(100);
		queue.setStatus(AutoProductQueue.Status.success);
		this.checkSuccess(queue.getCampaignID(), queue.getSequence(), queue.getUserID());
		queue.setLastModifiedTime(new Date());
		this.autoProductQueueRepository.save(queue);

		return true;
	}

	public boolean checkSuccess(long campaignID, long sequence, long userID) {
		List<AutoProductQueue> queues = this.autoProductQueueRepository.findByCampaignIDAndSequence(campaignID,
				sequence);
		int success = 0;
		int fail = 0;
		for (AutoProductQueue queue : queues) {
			if (queue.getStatus() == AutoProductQueue.Status.success) {
				success = success + 1;
			} else if (queue.getStatus() == AutoProductQueue.Status.failed) {
				fail = fail + 1;
			}
		}
		if (success + fail == queues.size()) {
			SinbadClient.MessageDto messDto = new SinbadClient.MessageDto();
			messDto.setContainerID(campaignID);
			messDto.setContainerUrl("/promote/status/campaign_id/" + campaignID + "?sequence=" + sequence);
			messDto.setContent("系统成功推广了" + success + "款宝贝,推广失败了" + fail + "款宝贝");
			messDto.setType("campaign");
			messDto.setUserID(userID);
			SinbadClient.MessageDto messDtos = this.sinbadClient.createMessage(userID, messDto);
			messDto.setNick(messDtos.getNick());
			log.debug("finish success:{}|fail:{}|queues.size():{},messageDto:{}", success, fail, queues.size(),
					messDto);
		}
		return true;
	}

	private List<String> parseWords(String str) {
		List<String> words = new ArrayList<String>();

		try {
			JsonNode root = this.objectMapper.reader().readTree(str);
			Iterator<String> it = root.fieldNames();
			while (it.hasNext()) {
				words.add(it.next());
			}
		} catch (JsonProcessingException e) {
			log.error("JsonProcessingException: {}", e);
		} catch (IOException e) {
			log.error("IOException: {}", e);
		}

		return words;
	}

	private String encodeWords(List<String> words) {
		String str = "";
		Map<String, Integer> map = new HashMap<String, Integer>();
		for (String word : words) {
			map.put(word, 51);
		}

		try {
			str = this.objectMapper.writer().writeValueAsString(map);
		} catch (JsonProcessingException e) {
			log.error("JsonProcessingException: {}", e);
		}

		return str;

	}

	@Data
	public static class KeywordVo {
		@JsonProperty("keyword_id")
		private long keywordID;
		private String word;
		@JsonProperty("max_price")
		private float maxPrice;
	}
}
